package org.ihelpuoo.weboo.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathExpressionException;

import org.ihelpuoo.io.CharInputStream;
import org.ihelpuoo.io.StringBufferOutputStream;
import org.ihelpuoo.lang.LoaderException;
import org.ihelpuoo.lang.NullIterator;
import org.ihelpuoo.lang.RenderException;
import org.ihelpuoo.parser.AbstractTextEngine;
import org.ihelpuoo.parser.segment.Segment;
import org.ihelpuoo.xml.XMLUtils;
import org.ihelpuoo.xml.XMLWalker;
import org.ihelpuoo.xml.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

public class XMLPatternSegment extends AbstractTextEngine implements Segment
{

	private static final String	PLUGID	= "plugid";

	public XMLPatternSegment()
	{
	}

	public XMLPatternSegment(Reader r) throws LoaderException
	{
		this();
		parse(r);
	}

	public XMLPatternSegment(InputStream ins) throws LoaderException
	{
		this();
		parse(ins);
	}

	public XMLPatternSegment(CharSequence cs)
	{
		this();
		try
		{
			parse(new CharInputStream(cs));
		}
		catch (LoaderException e)
		{
			throw new RuntimeException(e);
		}
	}

	public void clearAll()
	{
		if (null == mapping)
			return;
		Iterator<String> it = mapping.keySet().iterator();
		while (it.hasNext())
			clear(it.next());
	}

	public void clear(String name)
	{
		List<Element> list = mapping.get(name);
		if (null == list)
			return;
		Iterator<Element> e = list.iterator();
		while (e.hasNext())
			XMLUtils.removeAllChildren(e.next());
	}

	public Segment duplicate()
	{
		XMLPatternSegment seg = new XMLPatternSegment();
		if (null != doc)
		{
			seg.doc = (Document)doc.cloneNode(true);
			try
			{
				seg.init();
			}
			catch (XPathExpressionException e)
			{
				throw new RuntimeException(e);
			}
		}
		return seg;
	}

	public boolean isEquals(Segment seg)
	{
		if (null == doc)
			return equals(seg);
		XMLPatternSegment me = (XMLPatternSegment)this.duplicate();
		XMLPatternSegment that = (XMLPatternSegment)seg.duplicate();
		me.clearAll();
		that.clearAll();
		if (!XMLUtils.equalsIgnoreBlank(me.doc,that.doc))
			return false;
		return true;
	}

	public Iterator<String> iterator()
	{
		if (null == doc)
			return new NullIterator<String>();
		return mapping.keySet().iterator();
	}

	public CharSequence render()
	{
		if (null == doc)
			return "";
		StringBuffer sb = new StringBuffer();
		XMLWriter w = XMLWriter.wrap(new OutputStreamWriter(new StringBufferOutputStream(sb)));
		try
		{
			w.write(doc);
			w.flush();
		}
		catch (IOException e)
		{
			throw new RuntimeException(e);
		}
		return sb;
	}

	public Segment set(String key, boolean v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, int v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, double v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, float v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, long v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, byte v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, short v)
	{
		return set(key,String.valueOf(v));
	}

	public Segment set(String key, Object v)
	{
		if (null == doc)
			return this;
		List<Element> eles = mapping.get(key);
		if (null == eles)
			return this;
		Iterator<Element> it = eles.iterator();
		while (it.hasNext())
			XMLUtils.removeAllChildren(it.next());
		return _add(v,eles);
	}

	public Segment add(String key, Object v)
	{
		if (null == doc)
			return this;
		List<Element> eles = mapping.get(key);
		if (null == eles)
			return this;
		return _add(v,eles);
	}

	private Segment _add(Object v, List<Element> eles)
	{
		Iterator<Element> it = eles.iterator();
		while (it.hasNext())
			_add(v,it.next());
		return this;
	}

	// @ TODO ZZH: still has problem when compose segments.
	private boolean _add(Object v, Element ele)
	{
		Node nv;
		if (v instanceof Node && ((Node)v).getOwnerDocument() != doc)
			nv = XMLUtils.dupicate((Node)v,doc);
		else if (v instanceof XMLPatternSegment)
			nv = XMLUtils.dupicate(((XMLPatternSegment)v).doc.getDocumentElement(),doc);
		else
			nv = doc.createTextNode(v.toString());

		ele.appendChild(nv);
		return true;
	}

	public int size()
	{
		if (null == mapping)
			return 0;
		return mapping.size();
	}

	private Document					doc;
	private Map<String, List<Element>>	mapping;

	public void parse(Reader r) throws LoaderException
	{
		try
		{
			doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
					new InputSource(r));
			init();
		}
		catch (Exception e)
		{
			throw new LoaderException(e);
		}
	}

	private void init() throws XPathExpressionException
	{
		mapping = new HashMap<String, List<Element>>();
		Iterator<Node> it = new XMLWalker(doc);
		while (it.hasNext())
		{
			Node n = it.next();
			if (n instanceof Element)
			{
				if (((Element)n).hasAttribute(PLUGID))
				{
					String key = ((Element)n).getAttribute(PLUGID);
					List<Element> list = mapping.get(key);
					if (null == list)
					{
						list = new LinkedList<Element>();
						list.add((Element)n);
						mapping.put(key,list);
					}
					else
						list.add((Element)n);
				}
			}
		}
	}

	public Document getDocument()
	{
		return doc;
	}

	public Document getPureDocument()
	{
		if (null == doc)
			return null;
		Document pure = (Document)doc.cloneNode(true);
		Iterator<Node> it = new XMLWalker(pure);
		while (it.hasNext())
		{
			Node n = it.next();
			if (n instanceof Element)
				((Element)n).removeAttribute(PLUGID);
		}
		return pure;
	}

	public String getOriginContent()
	{
		if (null == doc)
			return null;
		XMLPatternSegment seg = (XMLPatternSegment)duplicate();
		seg.clearAll();
		StringBuffer sb = new StringBuffer();
		try
		{
			XMLWriter w = XMLWriter.wrap(new OutputStreamWriter(new StringBufferOutputStream(sb)));
			w.write(seg.doc);
			w.flush();
			w.close();
			return sb.toString();
		}
		catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	public void render2(Writer w) throws RenderException
	{
		Document x = this.getPureDocument();
		if (null == x)
			return;
		XMLWriter xw = XMLWriter.wrap(w);
		xw.setAutoFormat(false);
		try
		{
			xw.write(x.getDocumentElement(),0);
			xw.flush();
		}
		catch (IOException e)
		{
			throw new RenderException(e);
		}
	}

	public boolean contains(String key)
	{
		return mapping.containsKey(key);
	}

}
